package dungeonPackage;

import static org.junit.Assert.*;

import java.math.BigDecimal;
import java.util.*;

import org.junit.*;

import com.google.common.base.Predicate;

import squareBorderPackage.*;
import temperaturePackage.*;

/**
 * A class collecting tests for the class of dungeons.
 * 
 * @author Jessa Bekker & Esther Alberts
 * @version 1.0
 */
public class DungeonTest implements Predicate<Square>{

	/**
	 * Class variables referencing dungeons that may change during the test
	 * case.
	 */
	private Dungeon<Square> defaultDungeon;
	
	/**
	 * Class variables referencing composite dungeons that may change during the test
	 * case.
	 */
	private CompositeDungeon<Square> motherDungeon;

	/**
	 * Class variables referencing squares that may change during the test case.
	 */
	private Square square6, square7, square8, square9, square10, squareToSet;
	
	/**
	 * Class variables referencing dungeons that do not change during the entire
	 * test case.
	 */
	private static Level<Square> wetDungeon;

	/**
	 * Class variables referencing squares that do not change during the entire
	 * test case.
	 */
	private static Square wetSquare, square1, square2, square3, square4,
			square5, squareWithNullBorder;

	/**
	 * Class variables referencing orientations that do not change during the
	 * entire test case.
	 */
	private static Orientation maxLevel, max, reference, sameOrientation, outside, orientation1,
			orientation2, orientation3, orientation4, orientation5,
			orientation6;

	/**
	 * set up an immutable test fixture.
	 * 
	 * @post	The variable maxLevel references a new Orientation with coordinates (10,10,0)
	 * @post	The variable max references a new Orientation with coordinates (20,20,20)
	 * @post	The variable reference references a new Orientation with coordinates (4,4,4)
	 * @post	The variable sameOrientation references a new Orientation with coordinates (1,1,1)
	 * @post	The variable orientation1 references a new Orientation with coordinates (1,2,0)
	 * @post	The variable orientation2 references a new Orientation with coordinates (5,3,0)
	 * @post	The variable orientation3 references a new Orientation with coordinates (2,5,0)
	 * @post	The variable orientation4 references a new Orientation with coordinates (4,5,0)
	 * @post	The variable orientation5 references a new Orientation with coordinates (1,1,0)
	 * @post	The variable orientation6 references a new Orientation with coordinates (2,2,0)
	 * @post	The variable wetDungeon references a new Level with maxCoordinates maxLevel
	 * @post	The variable wetSquare references a new NormalSquare with temperature of 15 degree celsius
	 * 			and humidity of ten percent.
	 * @post	The variables square1, square2, square3, square4, square5 and squareWithNullBorder reference 
	 * 			a new NormalSquare with a temperature of 25 degree celsius and humidity of ten percent.
	 * @post	In wetDungeon, square1 is placed at orientation1, square2 is placed at orientation2, square3 is placed at orientation3, 
	 * 			square4 is placed at orientation4 and square5 is placed at orientation5.
	 * @post	square1 and wetSquare have a slippery wall in their floor.
	 * @post	squareWithNullBorder has no border in its east direction.
	 */
	@BeforeClass
	public static void setUpBeforeClass(){
		maxLevel = new Orientation(10, 10, 0);
		max = new Orientation(20, 20, 20);
		reference = new Orientation(4, 4, 4);
		sameOrientation = new Orientation(1, 1, 1);
		orientation1 = new Orientation(1, 2, 0);
		orientation2 = new Orientation(5, 3, 0);
		orientation3 = new Orientation(2, 5, 0);
		orientation4 = new Orientation(4, 5, 0);
		orientation5 = new Orientation(1, 1, 0);
		orientation6 = new Orientation(2, 2, 0);
		
		wetDungeon = new Level<Square>(maxLevel);
		
		wetSquare = new NormalSquare(new Temperature(15, TemperatureUnit.CELSIUS),
				BigDecimal.TEN);
		square1 = new NormalSquare();
		square2 = new NormalSquare();
		square3 = new NormalSquare();
		square4 = new NormalSquare();
		square5 = new NormalSquare();
		
		wetDungeon.setSquareAt(orientation1, square1);
		wetDungeon.setSquareAt(orientation2, square2);
		wetDungeon.setSquareAt(orientation3, square3);
		wetDungeon.setSquareAt(orientation4, square4);
		wetDungeon.setSquareAt(orientation5, square5);
		
		new Wall(true, wetSquare, Direction.FLOOR);
		new Wall(true, square1, Direction.FLOOR);
		
		squareWithNullBorder = new NormalSquare();
		squareWithNullBorder.getBorderAt(Direction.EAST).removeSquareAt(Direction.EAST);
	}

	/**
	 * set up a mutable test fixture.
	 * 
	 * @post	The variable defaultDungeon references a new Level with maxCoordinates maxLevel
	 * @post	The variable motherDungeon references a new CompositeDungeon with maxCoordinates max
	 * @post	The variables square6, square7, square8, square9, square10 and squareWithNullBorder reference 
	 * 			a new NormalSquare with a temperature of 25 degree celsius and humidity of ten percent.
	 * @post	The variable squareToSet references a new NormalSquare with temperature of 10 degree celsius
	 * 			and humidity of ten percent.
	 * @post	In wetDungeon, square6 is placed at orientation1, square7 is placed at orientation2, square8 is placed at orientation3, 
	 * 			square9 is placed at orientation4 and square10 is placed at orientation5.
	 * @post	defaultDungeon is inserted in motherdungeon at the orientation reference.
	 */
	@Before
	public void setUp(){
		
		defaultDungeon = new Level<Square>(maxLevel);
		motherDungeon = new CompositeDungeon<Square>(max);
		
		square6 = new NormalSquare();
		square7 = new NormalSquare();
		square8 = new NormalSquare();
		square9 = new NormalSquare();
		square10 = new NormalSquare();
		squareToSet = new NormalSquare(new Temperature(10, TemperatureUnit.CELSIUS),
				BigDecimal.TEN);
		defaultDungeon.setSquareAt(orientation1, square6);
		defaultDungeon.setSquareAt(orientation2, square7);
		defaultDungeon.setSquareAt(orientation3, square8);
		defaultDungeon.setSquareAt(orientation4, square9);
		defaultDungeon.setSquareAt(orientation5, square10);
		
		motherDungeon.insertSubDungeon(reference, defaultDungeon);
	}

	@Test
	public void initializeObject_LegalCase() {
		Dungeon<Square> dungeon = new Level<Square>(new Orientation(10, 20, 0));
		assertTrue(dungeon.getMaxCoordinates().equals(
				new Orientation(10, 20, 0)));
	}

	@Test(expected = IllegalArgumentException.class)
	public void initializeObject_IllegalCase() throws Exception {
		new Level<Square>(null);
	}
	
	@Test
	public void terminate_noMother() {
		motherDungeon.terminate();
		assertTrue(motherDungeon.isTerminated());
	}
	
	@Test
	public void terminate_hasMother() {
		defaultDungeon.terminate();
		assertTrue(defaultDungeon.isTerminated());
		assertFalse(defaultDungeon.hasMotherDungeon());
		assertFalse(motherDungeon.hasAsDirectSubDungeon(defaultDungeon));
	}

	
	@Test
	public void canHaveAsMaxCoordinates_TrueBigger() {
		assertTrue(defaultDungeon.canHaveAsMaxCoordinates(new Orientation(20,
				30, 0)));
	}

	@Test
	public void canHaveAsMaxCoordinates_TrueSame() {
		assertTrue(defaultDungeon.canHaveAsMaxCoordinates(new Orientation(10,
				10, 0)));
	}

	@Test
	public void canHaveAsMaxCoordinates_FalseNull() {
		assertFalse(defaultDungeon.canHaveAsMaxCoordinates(null));
	}

	@Test
	public void canHaveAsMaxCoordinates_FalseSmaller() {
		assertFalse(defaultDungeon.canHaveAsMaxCoordinates(new Orientation(9,
				10, 10)));
	}

	@Test
	public void setMaxCoordinates_LegalCase() {
		defaultDungeon.setMaxCoordinates(new Orientation(20, 30, 0));
		assertTrue(defaultDungeon.getMaxCoordinates().equals(
				new Orientation(20, 30, 0)));
	}

	@Test(expected = IllegalArgumentException.class)
	public void setMaxCoordinates_IllegalCase() throws Exception {
		defaultDungeon.setMaxCoordinates(new Orientation(5, 5, 5));
	}
	
	
	@Test
	public void getSquareAt_Null() throws Exception {
		assertEquals(defaultDungeon.getSquareAt(null), null);
	}

	@Test
	public void getSquareAt_orientationOutside() {
		assertTrue(defaultDungeon.getSquareAt(new Orientation(10, 11, 12)) == null);
	}

	@Test
	public void getSquareAt_noSquare() {
		assertTrue(defaultDungeon.getSquareAt(new Orientation(8, 8, 8)) == null);
	}

	@Test
	public void getSquareAt_aSquare() {
		assertTrue(defaultDungeon.getSquareAt(new Orientation(1, 2, 0)) == square6);
	}
	

	@Test
	public void getSquares_size() {
		assertTrue(defaultDungeon.getSquares().size() == 5);
	}

	@Test
	public void getSquares_contains() {
		Map<Orientation, Square> squares = defaultDungeon.getSquares();
		assertTrue(squares.containsKey(orientation1)
				&& squares.get(orientation1).equals(square6));
		assertTrue(squares.containsKey(orientation2)
				&& squares.get(orientation2).equals(square7));
		assertTrue(squares.containsKey(orientation3)
				&& squares.get(orientation3).equals(square8));
		assertTrue(squares.containsKey(orientation4)
				&& squares.get(orientation4).equals(square9));
		assertTrue(squares.containsKey(orientation5)
				&& squares.get(orientation5).equals(square10));
	}

	@Test
	public void isValidOrientation_null() {
		assertFalse(defaultDungeon.isValidOrientation(null));
	}
	
	@Test
	public void isValidOrientation_true() {
		assertTrue(defaultDungeon.isValidOrientation(orientation5));
	}
	
	@Test
	public void canHaveAsOrientation_notValid() {
		assertFalse(defaultDungeon.canHaveAsOrientation(null));
	}

	@Test
	public void canHaveAsOrientation_sameCoordinates() {
		assertFalse(defaultDungeon.canHaveAsOrientation(sameOrientation));
	}

	@Test
	public void canHaveAsOrientation_outside() {
		assertFalse(defaultDungeon.canHaveAsOrientation(outside));
	}

	@Test
	public void canHaveAsOrientation_True() {
		assertTrue(defaultDungeon.canHaveAsOrientation(orientation3));
	}

	@Test
	public void isValidSquare_True() {
		assertTrue(defaultDungeon.isValidSquare(square8));
	}
	
	@Test
	public void isValidSquare_IllegalCase() {
		assertFalse(defaultDungeon.isValidSquare(null));
	}
	
	@Test
	public void isValidSquare_nullBorders() {	
		assertFalse(defaultDungeon.isValidSquare(squareWithNullBorder));
	}

	@Test
	public void canHaveAsSquareAt_invalidSquare() {
		assertFalse(defaultDungeon.canHaveAsSquareAt(null, orientation3));
	}

	@Test
	public void canHaveAsSquareAt_invalidOrientation() {
		assertFalse(defaultDungeon.canHaveAsSquareAt(square3, null));
	}

	@Test
	public void canHaveAsSquareAt_repeatSquare() {
		assertFalse(defaultDungeon.canHaveAsSquareAt(square6, orientation3));
	}

	@Test
	public void canHaveAsSquareAt_repeatSquareSamePlace() {
		assertTrue(defaultDungeon.canHaveAsSquareAt(square6, orientation1));
	}
	
	@Test
	public void canHaveAsSquareAt_slipperyFalse() {
		assertFalse(wetDungeon.canHaveAsSquareAt(wetSquare, orientation5));
	}
	
	@Test
	public void canHaveAsSquareAt_slipperyTrue() {
		assertTrue(wetDungeon.canHaveAsSquareAt(wetSquare, orientation1));
	}

	@Test
	public void canHaveAsSquareAt_True() {
		assertTrue(defaultDungeon.canHaveAsSquareAt(square1, orientation3));
	}

	@Test(expected = IllegalArgumentException.class)
	public void hasSquareAt_IllegalCase() throws Exception {
		defaultDungeon.hasSquareAt(null);
	}

	@Test
	public void hasSquareAt_True() {
		assertTrue(defaultDungeon.hasSquareAt(orientation2));
	}

	@Test
	public void hasSquareAt_False() {
		assertFalse(defaultDungeon.hasSquareAt(sameOrientation));
	}

	@Test(expected = IllegalArgumentException.class)
	public void removeSquareAt_IllegalCase() throws Exception {
		defaultDungeon.removeSquareAt(null);
	}

	@Test
	public void removeSquare_LegalCase() {
		assertTrue(defaultDungeon.getEdgeSquares().containsValue(square6));
		assertTrue(square6.isInSameSpace(square10));
		defaultDungeon.removeSquareAt(orientation1);
		assertFalse(defaultDungeon.hasSquareAt(orientation1));
		assertFalse(square6.isInSameSpace(square10));
		assertFalse(square10.isInSameSpace(square6));
		assertTrue(square6.isTerminated());
		assertFalse(defaultDungeon.getEdgeSquares().containsValue(square6));
	}

	@Test(expected = IllegalArgumentException.class)
	public void setSquareAt_IllegalCase() throws Exception {
		defaultDungeon.setSquareAt(orientation1, null);
	}

	@Test(expected = IllegalArgumentException.class)
	public void setSquareAt_tooSlippery() throws Exception {
		wetDungeon.setSquareAt(orientation5, wetSquare);
	}

	@Test
	public void setSquareAt_LegalCase() {
		defaultDungeon.setSquareAt(orientation6, squareToSet);
		assertTrue(defaultDungeon.getSquareAt(orientation6) == squareToSet);
		assertTrue(squareToSet.getTemperature().getNumeral() == 20);
		assertTrue(squareToSet.getHumidity().equals(new BigDecimal(20).setScale(2, BigDecimal.ROUND_HALF_UP)));
		assertTrue(squareToSet.getSpace().containsAll(square6.getSpace())
				&& square6.getSpace().containsAll(squareToSet.getSpace()));
		assertTrue(squareToSet.getTemperature().equals(square6.getTemperature())
				&& square6.getTemperature().equals(squareToSet.getTemperature()));
		assertTrue(squareToSet.getHumidity().equals(square6.getHumidity()));
		assertTrue(squareToSet.getBorderAt(Direction.WEST) == square6
				.getBorderAt(Direction.EAST));
		assertTrue(defaultDungeon.getEdgeSquares().containsValue(squareToSet));
	}
	
	@Test
	public void isEdgeOrientation_True() {
		assertTrue(defaultDungeon.isEdgeOrientation(orientation1));
	}
	
	@Test
	public void isEdgeOrientation_False() {
		assertFalse(defaultDungeon.isEdgeOrientation(new Orientation(2,3,4)));
	}

	@Test
	public void getNbSlippery() {
		assertTrue(wetDungeon.getNumberSlippery() == 1);
	}

	@Test
	public void getSlipperyPercentage() {
		assertTrue(wetDungeon.getSlipperyPercentage() == 20);
	}

	@Test
	public void canAddSlipperyFlooring_True() {
		assertTrue(defaultDungeon.canAddSlipperyFlooring());
	}

	@Test
	public void canAddSlipperyFlooring_False() {
		assertFalse(wetDungeon.canAddSlipperyFlooring());
	}
	
	@Test(expected = IllegalArgumentException.class)
	public void getNeighbors_IllegalCase() throws Exception{
		defaultDungeon.getNeighbors(null);
	}

	@Test
	public void getNeighbors_LegalCase() {
		Set<Orientation> neigbors = defaultDungeon.getNeighbors(orientation1);
		assertTrue(neigbors.size() == 1 && neigbors.contains(orientation5));
	}
	
	@Test
	public void getRoot_LegalCase() {
		CompositeDungeon<Square> root = new CompositeDungeon<Square>(new Orientation(100, 100, 100));
		root.insertSubDungeon(orientation6, motherDungeon);
		assertTrue(defaultDungeon.getRoot() == root);
	}
	
	@Test
	public void hasMother_True() {
		assertTrue(defaultDungeon.hasMotherDungeon());
	}
	
	@Test
	public void hasMother_False() {
		assertFalse(motherDungeon.hasMotherDungeon());
	}
	
	@Test
	public void hasProperMotherDugneon() {
		assertTrue(defaultDungeon.hasProperMotherDungeon());
		assertTrue(motherDungeon.hasProperMotherDungeon());
		assertTrue(wetDungeon.hasProperMotherDungeon());
	}
	
	@Test
	public void getHotRocks() {
		RockSquare rock1 = new RockSquare();
		RockSquare rock2 = new RockSquare();
		RockSquare rock3 = new RockSquare();
		
		defaultDungeon.setSquareAt(new Orientation(1,1,0), rock1);
		defaultDungeon.setSquareAt(new Orientation(5,3,0), rock2);
		defaultDungeon.setSquareAt(new Orientation(1,9,0), rock3);
		
		Set<Square> hotRocks = defaultDungeon.getHotRocks();
		assertFalse(hotRocks.contains(rock1));
		assertFalse(hotRocks.contains(rock2));
		assertFalse(hotRocks.contains(rock3));
		
		((AccessibleSquare) square6).setTemperature(new Temperature(1000, TemperatureUnit.CELSIUS));
		assertTrue(rock1.getTemperature().getNumeral() == 1000);
		hotRocks = defaultDungeon.getHotRocks();
		assertTrue(hotRocks.contains(rock1));
		
	}
	@Test
	public void filteredIterator() {
		RockSquare rock1 = new RockSquare();
		RockSquare rock2 = new RockSquare();
		RockSquare rock3 = new RockSquare();
		
		defaultDungeon.setSquareAt(new Orientation(1,1,0), rock1);
		defaultDungeon.setSquareAt(new Orientation(5,3,0), rock2);
		defaultDungeon.setSquareAt(new Orientation(1,9,0), rock3);
		
		Set<Square> iteratedSquares = new HashSet<Square>();
		((AccessibleSquare) square6).setTemperature(new Temperature(1000, TemperatureUnit.CELSIUS));
		assertTrue(rock1.getTemperature().getNumeral() == 1000);
		
		Iterator<Square> iterator = defaultDungeon.filteredIterator(this);
		while(iterator.hasNext()) {
			iteratedSquares.add(iterator.next());
		}
		
		assertTrue(iteratedSquares.contains(rock1));
		assertFalse(iteratedSquares.contains(rock2));
		assertFalse(iteratedSquares.contains(rock3));
	}
	
	@Override
	public boolean apply(Square square) {
		return ((square instanceof RockSquare)
				&& square.getTemperature().toUnit(TemperatureUnit.CELSIUS).getNumeral() >= 200);
	}
}
